package com.kaibes.module.token.filter;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kaibes.core.base.util.StringUtils;
import com.kaibes.module.token.core.TokenUtils;
import com.kaibes.module.token.pojo.SecretToken;
import com.kaibes.module.token.pojo.Token;
import com.kaibes.module.token.service.TokenService;
import com.kaibes.web.encryption.EncryptionUtils;
import com.kaibes.web.filter.BeforeFilterInternal;
import com.kaibes.web.util.UserAgentUtils;

@Order(12)
@Component
public class SecretTokenBeforeFilter implements BeforeFilterInternal {

    @Value("${kaibes.module.token.is-debug:false}")
    private boolean isDebug;
    @Value("${kaibes.module.token.use-secret:false}")
    private boolean useSecret;
    @Autowired
    private EncryptionUtils encryptionUtils;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private TokenService tokenService;
    
    private void logout(Token token, HttpServletRequest request, HttpServletResponse response) {
        request.removeAttribute("token");
        tokenService.removeById(token.getId());
        TokenUtils.deleteAllFromCookie(request, response);
    }
    
    @Override
    public boolean beforeFilterInternal(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Token token = (Token) request.getAttribute("token");
        if (token == null) {
            return false;
        }
        if (!useSecret) {
            request.setAttribute("userId", token.getUserId());
            return false;
        }
        
        String uri = request.getRequestURI();
        String secretTokenStr = null;
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("secretToken")) {
                    secretTokenStr = cookie.getValue();
                    break;
                }
            }
        }
        
        if (secretTokenStr == null) {
            secretTokenStr = request.getHeader("secret-token");
        }
        
        if (secretTokenStr == null) {
            secretTokenStr = request.getParameter("secret-token");
        }
        
        // 清掉cookie，无论后面怎样了
        if (StringUtils.isBlank(secretTokenStr)) {
            if (isDebug) {
                LoggerFactory.getLogger(SecretTokenBeforeFilter.class).warn("Secret Token is blank !");
                request.setAttribute("userId", token.getUserId());
                return false;
            }
            
            if ((request.getMethod().equals("GET")
                    || !StringUtils.startWith(request.getContentType(), "multipart/form-data"))
                    && UserAgentUtils.isComeFromWeb(request)) {
                // GET方法可能是浏览器输入地址，这样就不会有，所以要验证一下
                String query = request.getQueryString();
                if (query != null) {
                    request.setAttribute("checkUrl", new StringBuilder(uri).append("?").append(query).toString());
                } else {
                    request.setAttribute("checkUrl", uri);
                }
                request.getRequestDispatcher("/token/check").forward(request, response);
                return true;
            } else {
                // 除了GET之外的方法都是非法的，无需验证，直接打死
                logout(token, request, response);
                return false;
            }
        } else {
            TokenUtils.deleteSecretTokenFromCookie(request, response);
            SecretToken secretToken;
            try {
                String json = encryptionUtils.decrypt2String(secretTokenStr, request);
                secretToken = objectMapper.readValue(json, SecretToken.class);
            } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
                    | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
                if (isDebug) {
                    e.printStackTrace();
                }
                logout(token, request, response);
                return false;
            }
            
            // 验证
            LocalDateTime createTime = secretToken.getCreateTime();
            long minutes = Duration.between(createTime, LocalDateTime.now()).toMinutes();
            
            // TODO 请求超时时间设置，配置后台设置
            if (minutes > 5) {
                logout(token, request, response);
                return false;
            }
            
            // TODO 防重发, 键一个表，保存uuid，判断是否已经存在，但这样一来就会耗费更多资源
            String method = secretToken.getMethod();
            if (method == null) {
                logout(token, request, response);
                return false;
            }
            
            method = method.toUpperCase();
            if (!request.getMethod().equals(method)) {
                logout(token, request, response);
                return false;
            }
            
            if (request.getQueryString() != null) {
                uri = new StringBuffer(uri).append("?").append(request.getQueryString()).toString();
            }
            String temp = secretToken.getUri();
            if (!uri.equals(temp)) {
                System.err.println(uri+"!="+temp);
                boolean flag = uri.equals("/") && StringUtils.isBlank(temp);
                if (!flag) {
                    logout(token, request, response);
                    return false;
                }
            }
            
            request.setAttribute("userId", token.getUserId());
        }
        
        return false;
    }

}
